package com.gitee.wsl.doc.prosemirror.model.content.ext

import com.gitee.wsl.doc.prosemirror.exception.ReplaceError
import com.gitee.wsl.doc.prosemirror.model.content.ResolvedPos
import com.gitee.wsl.doc.prosemirror.model.content.ResolvedPosRange
import com.gitee.wsl.doc.prosemirror.model.content._from
import com.gitee.wsl.doc.prosemirror.model.content._to
import com.gitee.wsl.doc.prosemirror.model.fragment.Fragment
import com.gitee.wsl.doc.prosemirror.model.fragment.Slice
import com.gitee.wsl.doc.prosemirror.model.node.Node
import com.gitee.wsl.doc.prosemirror.model.node.ext.addNode
import com.gitee.wsl.doc.prosemirror.model.node.ext.checkJoin
import com.gitee.wsl.doc.prosemirror.model.node.ext.close

fun ResolvedPosRange.replace(slice: Slice) = replace(this.first,this.second,slice)

internal fun replace(_from: ResolvedPos, _to: ResolvedPos, slice: Slice): Node {
    if (slice.openStart > _from.depth) {
        throw ReplaceError("Inserted content deeper than insertion position")
    }
    if (_from.depth - slice.openStart != _to.depth - slice.openEnd) {
        throw ReplaceError("Inconsistent open depths")
    }
    return replaceOuter(_from, _to, slice, 0)
}

fun ResolvedPosRange.replaceOuter(slice: Slice, depth: Int) = replaceOuter(this.first,this.second,slice,depth)
internal fun replaceOuter(_from: ResolvedPos, _to: ResolvedPos, slice: Slice, depth: Int): Node {
    val index = _from.index(depth)
    val node = _from.node(depth)
    return if (index == _to.index(depth) && depth < _from.depth - slice.openStart) {
        val inner = replaceOuter(_from, _to, slice, depth + 1)
        node.copy(node.content.replaceChild(index, inner))
    } else if (slice.content.size == 0) {
        node.close( replaceTwoWay(_from, _to, depth))
    } else if (slice.openStart == 0 && slice.openEnd == 0 && _from.depth == depth && _to.depth == depth) {
        // Simple, flat case
        val parent = _from.parent
        val content = parent.content
        val modified = content.cut(0, _from.parentOffset).append(slice.content).append(content.cut(_to.parentOffset))
        parent.close( modified)
    } else {
        val (start, end) = prepareSliceForReplace(slice, _from)
        node.close( replaceThreeWay(_from, start, end, _to, depth))
    }
}

fun ResolvedPosRange.joinable(depth: Int) = joinable(this.first,this.second,depth)
fun joinable(_before: ResolvedPos, _after: ResolvedPos, depth: Int): Node {
    val node = _before.node(depth)
    checkJoin(node, _after.node(depth))
    return node
}


fun ResolvedPosRange.addRange(depth: Int, target: MutableList<Node>)
      = addRange(this.first,this.second, depth,target)
fun addRange(_start: ResolvedPos?, _end: ResolvedPos?, depth: Int, target: MutableList<Node>) {
    val node = (_end ?: _start)!!.node(depth)
    var startIndex = 0
    val endIndex = _end?.index(depth) ?: node.childCount
    if (_start != null) {
        startIndex = _start.index(depth)
        if (_start.depth > depth) {
            startIndex++
        } else if (_start.textOffset != 0) {
            addNode(_start.nodeAfter!!, target)
            startIndex++
        }
    }
    for (i in startIndex until endIndex) addNode(node.child(i), target)
    if (_end != null && _end.depth == depth && _end.textOffset != 0) {
        addNode(_end.nodeBefore!!, target)
    }
}

fun ResolvedPosRange.replaceThreeWay(to:ResolvedPosRange,depth: Int)
         = replaceThreeWay(
              this.first, this.second,
              to.first, to.second,
               depth
            )
fun replaceThreeWay(
    _from: ResolvedPos,
    _start: ResolvedPos,
    _end: ResolvedPos,
    _to: ResolvedPos,
    depth: Int
): Fragment {
    val openStart = if (_from.depth > depth) joinable(_from, _start, depth + 1) else null
    val openEnd = if (_to.depth > depth) joinable(_end, _to, depth + 1) else null

    val content = mutableListOf<Node>()
    addRange(null, _from, depth, content)
    if (openStart != null && openEnd != null && _start.index(depth) == _end.index(depth)) {
        checkJoin(openStart, openEnd)
        addNode(openStart.close( replaceThreeWay(_from, _start, _end, _to, depth + 1)), content)
    } else {
        if (openStart != null) {
            addNode(openStart.close( replaceTwoWay(_from, _start, depth + 1)), content)
        }
        addRange(_start, _end, depth, content)
        if (openEnd != null) {
            addNode(openEnd.close( replaceTwoWay(_end, _to, depth + 1)), content)
        }
    }
    addRange(_to, null, depth, content)
    return Fragment(content)
}


fun ResolvedPosRange.replaceTwoWay(depth: Int) = replaceTwoWay(this._from,this._to,depth)
fun replaceTwoWay(_from: ResolvedPos, _to: ResolvedPos, depth: Int): Fragment {
    val content = mutableListOf<Node>()
    addRange(null, _from, depth, content)
    if (_from.depth > depth) {
        val type = joinable(_from, _to, depth + 1)
        addNode(type.close(replaceTwoWay(_from, _to, depth + 1)), content)
    }
    addRange(_to, null, depth, content)
    return Fragment(content)
}

fun prepareSliceForReplace(slice: Slice, _along: ResolvedPos): ResolvedPosRange {
    val extra = _along.depth - slice.openStart
    val parent = _along.node(extra)
    var node = parent.copy(slice.content)
    for (i in extra - 1 downTo 0) {
        node = _along.node(i).copy(Fragment.from(node))
    }
    return node.resolveNoCache(slice.openStart + extra) to
        node.resolveNoCache(node.content.size - slice.openEnd - extra)
}

fun Slice.prepareForReplace(_along: ResolvedPos): ResolvedPosRange =  prepareSliceForReplace(this,_along)
